from __future__ import annotations
from dataclasses import dataclass
from typing import List, Tuple, Dict, Any, Optional, Iterable
import numpy as np
import math
import random


from ..type.models import Missile, UAV, CloudModel, CylinderTarget, DropPlan
from .intervals import find_boolean_intervals, union_length, intervals_point_los_analytic
from .occlusion.base import PointLoSStrategy, CylinderSamplingStrategy, OcclusionStrategy
from src.core.analytical import segment_intersects_sphere, intervals_point_los_analytic_fixed


@dataclass
class Q1Config:
    # Target (cylinder)
    target_base_center: Tuple[float, float, float] = (0.0, 200.0, 0.0)
    target_radius: float = 7.0
    target_height: float = 10.0

    # Missile
    missile_M0: Tuple[float, float, float] = (20000.0, 0.0, 2000.0)
    missile_speed: float = 300.0
    missile_aim_to: Tuple[float, float, float] = (0.0, 0.0, 0.0)

    # UAV FY1 (heading toward origin at constant speed)
    uav_R0_xy: Tuple[float, float] = (17800.0, 0.0)
    uav_z: float = 1800.0
    uav_speed: float = 120.0

    # Drop plan timing
    t_rel: float = 1.5
    tau: float = 3.6

    # Cloud model
    cloud_radius: float = 10.0
    cloud_sink: float = 3.0
    cloud_life: float = 20.0
    g: float = 9.81

    # Time scan
    dt_scan: float = 0.005
    refine_iters: int = 60
    hit_margin: float = 0.5  # after missile hit time, we can stop

"""
Multi-point aggregated occlusion for Q1/Q2 (no time scanning).

核心思路
- 先为圆柱体生成一组代表点：重要点（center/top/bottom + 前向边缘）+ 随机侧面点
- 对每个点调用你已有的解析核 intervals_point_los_analytic_fixed(...) 得到区间
- 把所有区间转为带权事件，做一次“时间线扫”聚合：当覆盖度 S(t) >= eta 时记为遮蔽
- 输出最终聚合后的区间并集与总时长

依赖：你工程中已存在的以下对象/函数（无需修改）：
- Q1Config, Missile, UAV, CloudModel, CylinderTarget, DropPlan
- intervals_point_los_analytic_fixed(...)
"""


# ---------- 小工具 ----------
def _unit(v: np.ndarray) -> np.ndarray:
    v = np.asarray(v, float)
    n = np.linalg.norm(v)
    return v if n == 0 else v / n

def _merge_intervals(intervals: List[Tuple[float, float]], eps: float = 1e-10) -> List[Tuple[float, float]]:
    if not intervals:
        return []
    intervals = sorted(intervals)
    merged = [[intervals[0][0], intervals[0][1]]]
    for a, b in intervals[1:]:
        if a > merged[-1][1] + eps:
            merged.append([a, b])
        else:
            merged[-1][1] = max(merged[-1][1], b)
    return [(float(a), float(b)) for a, b in merged]

def _missile_dir(M0: np.ndarray, aim_to: np.ndarray) -> np.ndarray:
    return _unit(np.asarray(aim_to, float) - np.asarray(M0, float))

def _missile_hit_time(M0: np.ndarray, aim_to: np.ndarray, speed: float) -> float:
    return float(np.linalg.norm(np.asarray(aim_to, float) - np.asarray(M0, float))) / float(speed)

def _cyl_side_point(base_center: Tuple[float,float,float], R: float, H: float, theta: float, z_frac: float) -> np.ndarray:
    x0, y0, z0 = base_center
    z = z0 + z_frac * H
    return np.array([x0 + R * math.cos(theta), y0 + R * math.sin(theta), z], dtype=float)

# ---------- 代表点生成（重要点 + 分层随机） ----------
@dataclass
class AggPointsConfig:
    # 重要点：是否包含几何中心线（center/top/bottom）
    include_centerline: bool = True
    # 前向边缘角度（相对于“导弹相对目标的水平朝向”）
    rim_angles_deg: Tuple[float, ...] = (0.0, +15.0, -15.0, +30.0, -30.0, +45.0, -45.0)
    rim_heights: Tuple[str, ...] = ("bottom", "mid", "top")  # 三层
    rim_weight: float = 2.0  # 前缘加权（轮廓权重）
    # 随机侧面采样
    n_side_random: int =24
    side_weight: float = 1.0
    rng_seed: Optional[int] = None

def _height_to_frac(which: str) -> float:
    which = which.lower()
    if which in ("bottom", "low", "0"):
        return 0.0
    if which in ("mid", "middle", "center", "0.5"):
        return 0.5
    if which in ("top", "1", "high"):
        return 1.0
    raise ValueError(f"Unknown height spec: {which}")

def build_agg_points(
    target: "CylinderTarget",
    missile_M0: np.ndarray,
    missile_aim_to: np.ndarray,
    t_ref: float,
    cfg: AggPointsConfig
) -> Tuple[List[np.ndarray], List[float]]:
    """
    生成聚合代表点与对应权重。
    t_ref：参考时刻，用于确定“前向边缘”的朝向（建议靠近起爆后寿命中段/或命中前段）。
    """
    pts: List[np.ndarray] = []
    ws: List[float] = []
    x0, y0, z0 = target.base_center
    R = float(target.radius)
    H = float(target.height)

    # 1) 中心线（可选）
    if cfg.include_centerline:
        pts.append(np.array([x0, y0, z0 + 0.5 * H], float));   ws.append(1.0)  # center
        pts.append(np.array([x0, y0, z0 + H], float));          ws.append(1.0)  # top
        pts.append(np.array([x0, y0, z0], float));              ws.append(1.0)  # bottom

    # 2) 前向边缘：以 t_ref 时刻的“目标中心 -> 导弹位置”的水平投影方向为 0°
    Vm_dir = _missile_dir(np.asarray(missile_M0,float), np.asarray(missile_aim_to,float))
    Mt = np.asarray(missile_M0, float) + Vm_dir * 1.0 * t_ref * float(1.0 * 1.0)  # 仅方向相关，速度比例不影响方向
    T_center = np.array([x0, y0, z0 + 0.5 * H], float)
    e = Mt - T_center
    e[2] = 0.0
    if np.linalg.norm(e[:2]) < 1e-12:
        # 退化：导弹就在正上/下，取默认朝向 +x
        base_theta = 0.0
    else:
        e_xy = _unit(e[:2])
        base_theta = math.atan2(float(e_xy[1]), float(e_xy[0]))  # 前向角

    for ang in cfg.rim_angles_deg:
        theta = base_theta + math.radians(ang)
        for hz in cfg.rim_heights:
            fz = _height_to_frac(hz)
            pts.append(_cyl_side_point(target.base_center, R, H, theta, fz))
            ws.append(float(cfg.rim_weight))

    # 3) 随机侧面点（面积近似均匀）
    if cfg.n_side_random > 0:
        rng = random.Random(cfg.rng_seed)
        for _ in range(cfg.n_side_random):
            theta = rng.random() * 2.0 * math.pi
            fz = rng.random()
            pts.append(_cyl_side_point(target.base_center, R, H, theta, fz))
            ws.append(float(cfg.side_weight))

    return pts, ws

# ---------- 线扫聚合（不用时间扫描） ----------
def aggregate_intervals_weighted(
    per_point_intervals: List[List[Tuple[float, float]]],
    weights: List[float],
    eta: float = 1,
    hysteresis: float = 0.0,
    time_eps: float = 1e-9
) -> Tuple[List[Tuple[float, float]], float]:
    """
    把各点区间用带权线扫聚合：覆盖度 S(t) = sum(w_i * 1_{t∈I_i}) / sum(w_i) ≥ eta 视为遮蔽。
    支持滞回（hysteresis）：开启阈值 = eta，总是关闭阈值 = max(0, eta - hysteresis)。
    """
    assert len(per_point_intervals) == len(weights)
    Wtot = float(sum(weights)) if weights else 0.0
    if Wtot <= 0.0:
        return [], 0.0

    on_thr = eta * Wtot
    off_thr = max(0.0, (eta - hysteresis) * Wtot)

    # 收集“事件”：(t, +w) or (t, -w)
    events: List[Tuple[float, float]] = []
    for I, w in zip(per_point_intervals, weights):
        w = float(w)
        if w <= 0.0 or not I:
            continue
        for a, b in I:
            if b <= a:
                continue
            events.append((float(a), +w))
            events.append((float(b), -w))
    if not events:
        return [], 0.0

    # 合并“极近”的同时刻事件
    events.sort(key=lambda x: x[0])
    merged_events: List[Tuple[float, float]] = []
    cur_t = events[0][0]
    cur_d = 0.0
    for t, dw in events:
        if t <= cur_t + time_eps:
            cur_d += dw
        else:
            merged_events.append((cur_t, cur_d))
            cur_t = t
            cur_d = dw
    merged_events.append((cur_t, cur_d))

    # 线扫
    acc = 0.0
    inside = False
    start: Optional[float] = None
    out: List[Tuple[float, float]] = []

    # 为便于处理“片段区间”，构造采样点：事件时刻 + 终止“哨兵”
    times = [t for (t, _) in merged_events]
    times.append(times[-1])  # 哨兵（不会产生长度）

    for i in range(len(merged_events)):
        t_i, delta = merged_events[i]
        # 应用在 t_i 处的权重变化
        acc += delta

        # 下一个事件时刻
        t_next = times[i+1]
        if t_next < t_i + time_eps:
            continue  # 零宽片段

        # 基于滞回的进入/退出判定
        if not inside and acc >= on_thr - 1e-12:
            inside = True
            start = t_i
        elif inside and acc <= off_thr + 1e-12:
            inside = False
            if start is not None and t_i > start + time_eps:
                out.append((start, t_i))
            start = None

        # 若仍在内，当前片段 [t_i, t_next) 属于遮蔽
        # 但我们只在状态切换时写入，为简洁起见不在此追加

    # 扫描结束，若仍在内，封尾到最后一个事件时刻
    if inside and start is not None:
        last_t = merged_events[-1][0]
        if last_t > start + time_eps:
            out.append((start, last_t))

    out = _merge_intervals(out, eps=time_eps)
    total = float(sum(b - a for a, b in out))
    return out, total

# ---------- 顶层：多点聚合版解析评估 ----------
def solve_q1_analytic_aggregate(
    cfg: "Q1Config",
    eta: float = 0.5,
    hysteresis: float = 0.0,
    points_cfg: AggPointsConfig = AggPointsConfig(),
    t_ref_policy: str = "auto",   # "auto" or "t_det" or "hit_mid"
    return_per_point: bool = False,
) -> Dict[str, Any]:
    """
    多点聚合的 Q1 解析评估（可直接作为 Q2 的评估核）。
    - eta: 覆盖门槛（>=eta 记作遮蔽），0.5 表示“至少一半代表点被遮住”
    - hysteresis: 滞回（例如 0.05），避免端点抖动
    - points_cfg: 控制代表点的生成（前向边缘 + 随机侧面等）
    - t_ref_policy:
        "auto"   -> t_det + min(life/2, 0.3*t_hit)
        "t_det"  -> 直接用 t_det
        "hit_mid"-> 0.5 * t_hit
    - return_per_point: True 时回传每个代表点的区间（用于诊断/可视化）
    """
    # 场景装配（沿用你的 solve_q1_analytic）
    target  = CylinderTarget(cfg.target_base_center, cfg.target_radius, cfg.target_height)
    missile = Missile(cfg.missile_M0, cfg.missile_speed, cfg.missile_aim_to)
    uav     = UAV(cfg.uav_R0_xy, cfg.uav_z, cfg.uav_speed)
    cloud   = CloudModel(cfg.cloud_radius, cfg.cloud_sink, cfg.cloud_life, cfg.g)
    plan    = DropPlan(uav, cfg.t_rel, cfg.tau)
    plan.compute_detonation(cloud)

    # 时间窗
    t_hit = _missile_hit_time(np.array(cfg.missile_M0), np.array(cfg.missile_aim_to), cfg.missile_speed)
    t0 = plan.t_det
    t1 = min(plan.t_det + cloud.life, t_hit + cfg.hit_margin)
    if t1 <= t0:
        return {
            "t_det": plan.t_det, "xy_det": plan.xy_det, "z_det": plan.z_det,
            "intervals": [], "total_cover": 0.0,
            "eta": eta, "hysteresis": hysteresis,
            "diagnostics": {"reason": "empty_time_window"}
        }

    # 参考时刻（用于前向边缘朝向）
    if t_ref_policy == "t_det":
        t_ref = t0
    elif t_ref_policy == "hit_mid":
        t_ref = 0.5 * t_hit
    else:
        t_ref = t0 + min(0.5 * cloud.life, 0.3 * t_hit)

    # 生成代表点与权重
    pts, ws = build_agg_points(
        target=target,
        missile_M0=np.array(cfg.missile_M0, float),
        missile_aim_to=np.array(cfg.missile_aim_to, float),
        t_ref=float(t_ref),
        cfg=points_cfg
    )

    # 单点解析评估（对每个代表点）
    C_det = np.array([plan.xy_det[0], plan.xy_det[1], plan.z_det], float)
    per_point_intervals: List[List[Tuple[float,float]]] = []
    for Ti in pts:
        Ii, _ = intervals_point_los_analytic_fixed(
            M0=np.array(cfg.missile_M0, float),
            aim_to=np.array(cfg.missile_aim_to, float),
            Vm_speed=cfg.missile_speed,
            T=np.asarray(Ti, float),
            C_det=C_det,
            sink=cloud.sink,
            t_det=plan.t_det,
            r=cloud.r,
            t0=t0,
            t1=t1
        )
        per_point_intervals.append(Ii)

    # 带权线扫聚合
    agg_intervals, agg_total = aggregate_intervals_weighted(
        per_point_intervals=per_point_intervals,
        weights=ws,
        eta=eta,
        hysteresis=hysteresis,
        time_eps=1e-9
    )

    out: Dict[str, Any] = {
        "t_det": plan.t_det,
        "xy_det": plan.xy_det,
        "z_det": plan.z_det,
        "intervals": [(float(a), float(b)) for a, b in agg_intervals],
        "total_cover": float(agg_total),
        "eta": float(eta),
        "hysteresis": float(hysteresis),
        "points_used": len(pts),
        "weights_sum": float(sum(ws)),
        "t_window": (float(t0), float(t1)),
        "t_ref": float(t_ref),
        "points_cfg": points_cfg,
    }
    if return_per_point:
        # 仅用于诊断：返回每个点的区间与权重
        out["per_point"] = [
            {"T": (float(Ti[0]), float(Ti[1]), float(Ti[2])),
             "w": float(wi),
             "intervals": [(float(a), float(b)) for a,b in Ii]}
            for Ti, wi, Ii in zip(pts, ws, per_point_intervals)
        ]
    return out